﻿/**************************************************************
 *
 * 唯一标识：9ed5e0c8-1199-459f-b5f6-aa2053fd94d6
 * 命名空间：Sgr.Modules
 * 创建时间：2023/7/30 10:02:17
 * 机器名称：DESKTOP-S0D075D
 * 创建者：antho
 * 电子邮箱：fengqinhua2016@163.com
 * 描述：
 *
 **************************************************************/

using Sgr.Utilities;
using System.Reflection;

// 插件的技术实现
// 方式 A（Assembly.Load）适用于程序集在应用的 probing 路径或已作为依赖引用的情况——按名称加载，无法隔离，也不能卸载。
// 方式 B（AssemblyLoadContext，简称 ALC）是 .NET Core/.NET 的推荐插件方案：可以为每个插件创建独立加载上下文，控制依赖解析、实现隔离与可卸载（collectible ALC）。

// 我先简单使用方式A来实现，为保证插件能被加载，要求插件程序集必须放在应用的输出目录下，或者作为项目引用存在。

namespace Sgr.Modules
{
    /// <summary>
    /// 基于json配置获取模块信息（从指定目录中）
    /// </summary>
    public abstract class ConfigurationModuleInfoProviderBase : IModuleInfoProvider
    {
        /// <summary>
        /// 模块信息配置文件后缀名称
        /// </summary>
        public static readonly string ModulesFilename = "*.ModuleManifest.json";

        protected virtual void Warn(string message)
        {
            Console.WriteLine(message);
        }

        /// <summary>
        /// 获取模块信息
        /// </summary>
        /// <param name="environment"></param>
        /// <returns></returns>
        public IEnumerable<ModuleInfo> GetModules(IHostEnvironment environment)
        {
            ArgumentNullException.ThrowIfNull(environment);

            string modulesPath = GetModuleDirectory(environment);

            if (string.IsNullOrWhiteSpace(modulesPath))
            {
                Warn($"模块目录路径为空，跳过模块加载。");
                yield break;
            }

            if (!Directory.Exists(modulesPath))
            {
                Warn($"模块目录不存在，路径：{modulesPath}，跳过模块加载。");
                yield break;
            }

            // 使用流式枚举以减少内存占用
            IEnumerable<string> files;
            try
            {
                files = Directory.EnumerateFiles(modulesPath, ModulesFilename, SearchOption.TopDirectoryOnly);
            }
            catch (Exception ex)
            {
                Warn($"枚举模块配置文件时发生错误，目录：{modulesPath}，错误信息：{ex.Message}");
                yield break;
            }

            // 记录已加载的模块，避免重复加载
            Dictionary<string, ModuleManifest> modules = new Dictionary<string, ModuleManifest>();
            foreach (var file in files)
            {
                try
                {
                    var content = File.ReadAllText(file);

                    ModuleManifest? module = JsonHelper.DeserializeObject<ModuleManifest>(content);

                    if (module == null)
                    {
                        Warn($"模块配置文件内容无效，文件：{file}");
                        continue;
                    }

                    string id = (module.Id ?? string.Empty).Trim();
                    if (string.IsNullOrEmpty(id))
                    {
                        Warn($"模块配置文件缺少模块ID，文件：{file}");
                        continue;
                    }

                    if (!module.IsEnable)
                    {
                        Warn($"模块已被禁用，模块ID：{id}，文件：{file}，已跳过该配置文件的加载。");
                        continue;
                    }

                    if (modules.ContainsKey(id))
                    {
                        Warn($"检测到重复的模块配置，模块ID：{id}，文件：{file}，已跳过该配置文件的加载。");
                        continue;
                    }

                    // 正确归一化 ID
                    module.Id = id;
                    modules.Add(id, module);
                }
                catch (Exception ex)
                {
                    Warn($"加载模块配置文件时发生错误，文件：{file}，错误信息：{ex.Message}");
                }
            }

            // 执行加载模块程序集
            foreach (var module in modules.Values)
            {
                Assembly? assembly = null;
                try
                {
                    assembly = Assembly.Load(new AssemblyName(module.Id));
                }
                catch (Exception ex)
                {
                    Warn($"加载模块程序集时发生错误，模块ID：{module.Id}，错误信息：{ex.Message}");
                    assembly = null;
                }

                // 如果按名称加载失败，尝试从模块目录按文件名加载（回退）
                if (assembly == null)
                {
                    try
                    {
                        var dllPath = Path.Combine(modulesPath, module.Id + ".dll");
                        if (File.Exists(dllPath))
                        {
                            assembly = Assembly.LoadFrom(dllPath);
                        }
                    }
                    catch (Exception ex)
                    {
                        Warn($"按文件路径加载程序集时发生错误，模块ID：{module.Id}，错误信息：{ex.Message}");
                        assembly = null;
                    }
                }

                if (assembly == null)
                {
                    Warn($"未能加载模块程序集，模块ID：{module.Id}，请确保模块程序集已被引用或位于应用程序的输出目录中。");
                    continue;
                }

                Version? version = null;
                try
                {
                    version = assembly.GetName()?.Version;
                }
                catch
                {
                    // 忽略获取版本时的异常，版本字段可选
                    version = null;
                }

                yield return new ModuleInfo
                {
                    Id = module.Id,
                    Name = module.Name,
                    Category = module.Category,
                    Description = module.Description,
                    IsEnable = module.IsEnable,
                    Assembly = assembly,
                    Version = version
                };
            }
        }

        /// <summary>
        /// 获取模块所在目录
        /// </summary>
        /// <param name="environment"></param>
        /// <returns></returns>
        protected abstract string GetModuleDirectory(IHostEnvironment environment);
    }
}